`include "CacheDefines.svh"
`include "defines.svh"
`define CLOG2(x) ((x <= 1) || (x > 512)) ? 0 : (x <= 2) ? 1 : (x <= 4) ? 2 : (x <= 8) ? 3 : (x <= 16) ? 4 : (x <= 32) ? 5 : (x <= 64) ? 6 : (x <= 128) ? 7: (x <= 256) ? 8: (x <= 512) ? 9 : 0

module Icache #(
    parameter DATA_WIDTH    = 32,//cache和cpu 总线数据位宽为data_width
    parameter LINE_WORD_NUM = 4,//cache line大小 �??块的字数
    parameter ASSOC_NUM     = 2,//assoc_num组相�??
    parameter WAY_SIZE      = 4*1024*8,//�??路cache 容量大小为way_size bit
    parameter SET_NUM       = WAY_SIZE/(LINE_WORD_NUM*DATA_WIDTH) //256

) (
    input logic clk,
    input logic resetn,
    AXI_UICACHE_Interface axi_ubus,
    CPU_IBus_Interface cpu_bus,
    AXI_IBus_Interface axi_bus
    
);

localparam int unsigned BYTES_PER_WORD = 4;
localparam int unsigned INDEX_WIDTH    = $clog2(SET_NUM) ;  //8
localparam int unsigned OFFSET_WIDTH   = $clog2(LINE_WORD_NUM*BYTES_PER_WORD);  //4
localparam int unsigned TAG_WIDTH      = 32-INDEX_WIDTH-OFFSET_WIDTH ;  //20

typedef struct packed {
    logic valid;
    logic [TAG_WIDTH-1:0]tag;
} TagVType;
typedef logic [DATA_WIDTH-1:0] data_t;
typedef enum logic [3:0]
{
    REQ,
    WAIT,
    UNCACHEDONE,
    LOOKUP,
    MISSCLEAN,
    REFILL,
    REFILLDONE
} StateType;

typedef struct packed {
    logic valid;
    logic [TAG_WIDTH-1:0] tag;
    logic [INDEX_WIDTH-1:0] index;
    logic [OFFSET_WIDTH-1:0]offset;
    logic isCache;
} RequestType;

StateType state,state_next;

logic[DATA_WIDTH-1:0]uncache_rdata;







RequestType req_buffer;
logic req_buffer_en;

logic [SET_NUM-1:0]lru;//Least Recently Used
//logic [$clog2(ASSOC_NUM)-1:0] lru;
logic [ASSOC_NUM-1:0] hit;//single way hit

//the rewrite way
logic [$clog2(ASSOC_NUM)-1:0]rewrite_way;
assign rewrite_way = lru[req_buffer.index];
//assign rewrite_way = lru;

logic cache_hit;

TagVType tagv_rdata_buffer[ASSOC_NUM-1:0]; //buffer of tagv_rdata
logic tagv_buffer_wen; //Write Enable of tagv_rdata_buffer <= tagv_rdata


logic busy;

//rdata 
data_t cache_rdata_assoc_off[ASSOC_NUM-1:0][LINE_WORD_NUM-1:0];//32*4*4 all data with the same Index
logic[DATA_WIDTH-1:0] cache_rdata_assoc[ASSOC_NUM-1:0];//32*4 the same Index and the same Offset
logic[DATA_WIDTH-1:0] cache_rdata_end;//32 the same Index and same Offset and same Way

//cpu_bus
assign cpu_bus.busy = busy;
assign cpu_bus.rdata = (req_buffer.valid) ? cache_rdata_end : '0;

//axi_bus
assign axi_bus.rd_req = (state == MISSCLEAN) ? 1'b1:1'b0;
assign axi_bus.rd_addr = {req_buffer.tag , req_buffer.index, {OFFSET_WIDTH{1'b0}}};

//axi_ubus
assign axi_ubus.rd_req =(state == REQ) ? 1'b1:1'b0;
assign axi_ubus.rd_addr={req_buffer.tag , req_buffer.index, req_buffer.offset};

//debug information

logic[31:0]req_buffer_phy_addr;
assign req_buffer_phy_addr={req_buffer.tag , req_buffer.index, req_buffer.offset};



//REFILL TagV
logic[INDEX_WIDTH-1:0]read_addr;


TagVType tagv_rdata[ASSOC_NUM-1:0];  //read information of TagV Table with same index
TagVType tagv_wdata;                 //write information of TagV Table
logic[ASSOC_NUM-1:0]tagv_we;                    //write TagV Table Enable

//REFILL Data
logic data_read_en;
logic[DATA_WIDTH-1:0] data_wdata[LINE_WORD_NUM-1:0];//32*4 data from axi
logic[ASSOC_NUM-1:0] data_we;//write Data Table Enable
generate;
    for (genvar i = 0;i<ASSOC_NUM ;i++ ) begin //TagV Table
        simple_port_lutram  #(
            .SIZE(SET_NUM),
            .dtype(TagVType)
        ) mem_tag(
            .clka(clk),
            .rsta(~resetn),


            .ena(1'b1),
            .wea(tagv_we[i]),//write enable //when REFILL
            .addra(read_addr),//write or read addr
            .dina(tagv_wdata),
            .douta(tagv_rdata[i])
        );

        for (genvar j=0; j<LINE_WORD_NUM; ++j) begin //Data Table
            simple_port_ram #(
            .SIZE(SET_NUM)
        )mem_data(
            .clk(clk),
            .rst(~resetn),

            //write //When REFILL
            .ena(1'b1),
            .wea(data_we[i]),
            .addra(read_addr),
            .dina(data_wdata[j]),

            //read
            .enb(data_read_en),
            .addrb(read_addr),
            .doutb(cache_rdata_assoc_off[i][j])//Way:i  WordNum:j
        );
        end
        
    end
endgenerate

always_ff@(posedge clk)begin
    if(resetn == 1'b0)begin
            lru <= '0;
    end else if(cache_hit)begin
        if(hit[0])begin
                lru[req_buffer.index] <= 1'b1;
            end else begin
                lru[req_buffer.index] <= 1'b0;
            end
    end else begin
        lru <= lru;
    end
end


generate
    for(genvar i = 0 ; i < ASSOC_NUM ; i++)begin
        assign hit[i] = ( tagv_rdata_buffer[i].valid & (req_buffer.tag == tagv_rdata_buffer[i].tag) )  ; //hit
        assign cache_rdata_assoc[i] = cache_rdata_assoc_off[i][req_buffer.offset[OFFSET_WIDTH-1:2]];  // choose of 32*4*4 -> 32*4
    end
endgenerate

generate;//data from axi
    for (genvar i=0; i<LINE_WORD_NUM; i++) begin
        assign data_wdata[i] = axi_bus.ret_data[32*(i+1)-1:32*(i)]; 
    end
endgenerate

//read enable 
assign data_read_en = (state == REFILLDONE)?1'b1:(cpu_bus.stall)?1'b0:1'b1;
//rdata
assign cache_rdata_end = (req_buffer.valid) ? (state == UNCACHEDONE ? uncache_rdata : cache_rdata_assoc[hit[1]] ): '0;
//hit
assign cache_hit = |hit;
//Index addr
assign read_addr = (state == REFILLDONE || state == REFILL )? req_buffer.index : cpu_bus.index;

//busy
logic busy_cache;
logic busy_uncache;
assign busy_cache     = (req_buffer.valid & ~cache_hit & req_buffer.isCache) ? 1'b1:1'b0;
assign busy_uncache   = (req_buffer.valid & (~req_buffer.isCache) & (state != UNCACHEDONE) ) ?1'b1 :1'b0;
assign busy           = busy_cache | busy_uncache;
//write tagv_rdata_buffer enable
assign tagv_buffer_wen = (state == REFILLDONE) ? 1'b1 : (cpu_bus.stall) ? 1'b0 : 1'b1;
//renew req_buffer enable
assign req_buffer_en = (cpu_bus.stall) ? 1'b0 : 1'b1;

assign tagv_wdata =  {1'b1,req_buffer.tag} ;

//choose way depend on LRU when REFILL
always_comb begin 
    if (state == REFILL) begin
        tagv_we = '0;
        tagv_we[rewrite_way] =1'b1;
        data_we = '0;
        data_we[rewrite_way] =1'b1;
    end else begin
        tagv_we = '0;
        data_we = '0;
    end
end
//renew req_buffer
always_ff @(posedge clk) begin
    if (resetn == `RstEnable) begin
        req_buffer <='0;
    end else if(req_buffer_en) begin
        req_buffer <= {cpu_bus.valid,cpu_bus.tag,cpu_bus.index,cpu_bus.offset,cpu_bus.isCache};
    end else begin
        req_buffer <= req_buffer;
    end
end
//renew data from uncache
always_ff @( posedge clk ) begin
    if (axi_ubus.ret_valid) begin
        uncache_rdata <= axi_ubus.ret_data;
    end else begin
        uncache_rdata <= uncache_rdata;
    end
end
//renew tagv_rdata_buffer
generate;
    for (genvar  i=0; i<ASSOC_NUM; i++) begin
    always_ff @( posedge clk ) begin
        if (tagv_buffer_wen) begin
            tagv_rdata_buffer[i].tag   <= tagv_rdata[i].tag;
            tagv_rdata_buffer[i].valid <= tagv_rdata[i].valid ;
        end else begin
            tagv_rdata_buffer[i].tag   <= tagv_rdata_buffer[i].tag;
            tagv_rdata_buffer[i].valid <= tagv_rdata_buffer[i].valid ;        
        end
    end        
    end
endgenerate

always_ff @( posedge clk )begin
    if (resetn == 1'b0)
        state <= LOOKUP;
    else 
        state <= state_next;
end

always_comb begin
    state_next = LOOKUP ;
    unique case (state)
        LOOKUP:begin
            if (req_buffer.isCache == 1'b0 && req_buffer.valid) begin
                state_next = REQ;
            end else begin
            if (~cache_hit & req_buffer.valid) begin
                state_next = MISSCLEAN;
            end else begin
                state_next = LOOKUP ;
            end
            end 
        end
        MISSCLEAN:begin
            if (axi_bus.rd_rdy) begin
                state_next = REFILL;
            end else begin
                state_next = MISSCLEAN;
            end
        end
        REFILL:begin
            if (axi_bus.ret_valid) begin//值合�??
                state_next = REFILLDONE;
            end else begin
                state_next = REFILL;
            end
        end
        REFILLDONE:begin
                state_next = LOOKUP;
            
        end
        REQ:begin
            if (axi_ubus.rd_rdy) begin
                state_next = WAIT;
            end else begin
                state_next = REQ;
            end
        end
        WAIT:begin
            if (axi_ubus.ret_valid) begin
                state_next = UNCACHEDONE;
            end else begin
                state_next = WAIT;
            end
        end
        UNCACHEDONE:begin
            if (cpu_bus.stall) begin
                state_next = UNCACHEDONE;
            end else begin
                state_next = LOOKUP;
            end
        end
        default:begin
            state_next = LOOKUP;
        end
    endcase
end

endmodule
